package com.senthink.gl.business.auth;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.senthink.gl.business.area.AreaBoxRequest;
import com.senthink.gl.business.common.exception.BusinessException;
import com.senthink.gl.business.common.exception.DataAccessException;
import com.senthink.gl.business.common.util.RandomUtils;
import com.senthink.gl.business.domain.entity.*;
import com.senthink.gl.business.domain.service.*;
import com.senthink.gl.business.fault.DeviceTypeEnum;
import com.senthink.gl.business.messages.CodeMsg;
import com.senthink.gl.business.response.ApiResponse;
import com.senthink.gl.business.response.CodeMsgDataResponse;
import com.senthink.gl.business.security.CurrentUser;
import com.senthink.gl.business.security.JwtTokenUtils;
import com.senthink.gl.business.security.SecurityUtils;
import com.senthink.gl.business.userlog.SystemUserLog;
import com.senthink.gl.business.userlog.enums.LogDetailEnum;
import com.senthink.gl.business.userlog.enums.LogOperateEnum;
import com.senthink.gl.business.userlog.enums.LogTypeEnum;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.util.*;

/**
 * @author Ethan
 * @date 2017/11/16
 */
@Service
public class AuthService {

    @Autowired
    private CodeMsg codeMsg;
    @Value("${jwt.prefix}")
    private String tokenPrefix;
    @Autowired
    private UserService userService;
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private JwtTokenUtils jwtTokenUtils;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private AreaDataService areaDataService;
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserAreaAuthorityDataService areaAuthorityDataService;
    @Autowired
    private UserMenuAuthorityDataService menuAuthorityDataService;


    private static final Logger LOGGER = LoggerFactory.getLogger(AuthService.class);

    /**
     * 添加注册用户
     *
     * @param userDTO userDTO
     * @return an instance of {@link ApiResponse}
     */
    @SystemUserLog(operate = LogOperateEnum.CREATE, detail = LogDetailEnum.CREATE_USER, logType = LogTypeEnum.USER)
    @Transactional(rollbackFor = {DataAccessException.class, BusinessException.class})
    public ApiResponse register(RegisterUserRequest userDTO) {
        boolean isExistByAcoount = userService.isUserExistByAccount(userDTO.getAccount());
        boolean isExistByMobile = userService.isUserExistByMobile(userDTO.getMobile());

        if (isExistByAcoount) {
            throw new BusinessException(codeMsg.userExistCode(), codeMsg.accountExistMsg());
        }
        if (isExistByMobile) {
            throw new BusinessException(codeMsg.userExistCode(), codeMsg.phoneExistMsg());
        }
        final String password = userDTO.getPassword();
        userDTO.setPassword(passwordEncoder.encode(password));

        User user = RegisterUserRequest.convertTo(userDTO);
        if(userDTO.getAid()!=null) {
            Optional<Area> area = areaDataService.findByAid(userDTO.getAid());
            if(area.isPresent()) {
                user.setArea(area.get());
            }
        }
        user.setLastPwdRestDate(new Date());
        user.setUid(RandomUtils.randomString(30));
        user.setEnable(true);
        user.setRole("ROLE_ROOT");
        user = userService.saveUser(user);

        return new CodeMsgDataResponse(codeMsg.successCode(), codeMsg.successMsg());
    }

    /**
     * 删除权限信息(区域权限、菜单权限)
     *
     * @param uid
     */
    public void deleteAuthorities(String uid) {
        areaAuthorityDataService.deleteAllByUid(uid);
        menuAuthorityDataService.deleteAllByUid(uid);
    }



    /**
     * 用户登录
     *
     * @param username 用户标识
     * @param password 密码
     * @return an instance of {@link ApiResponse}
     */
    @SystemUserLog(operate = LogOperateEnum.LOGIN_IN, detail = LogDetailEnum.LOGIN_IN)
    public ApiResponse login(String username, String password) {
        UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password);
        Authentication authentication;
        try {
            authentication = authenticationManager.authenticate(upToken);
        } catch (DisabledException e) {
            throw new BusinessException(codeMsg.failureCode(), "用户已被禁用！");
        } catch (Exception e) {
            throw new BusinessException(codeMsg.accountErrorCode(), codeMsg.accountErrorMsg());
        }
        SecurityContextHolder.getContext().setAuthentication(authentication);

        CurrentUser currentUser = (CurrentUser) authentication.getPrincipal();
        String token = jwtTokenUtils.generateToken(currentUser);
        long expireAt = jwtTokenUtils.getExpirationDateFromToken(token).getTime();
        TokenResponse tokenResponse = new TokenResponse(token, expireAt, currentUser.getRole());
        System.out.println("token验证码为：" + token);
        return new CodeMsgDataResponse<>(codeMsg.successCode(), codeMsg.successMsg(), tokenResponse);
    }

    /**
     * 刷新Token操作
     *
     * @param oldToken 旧的Token
     * @return an instance of {@link ApiResponse}
     */
    public ApiResponse refresh(String oldToken) {
        if (StringUtils.isBlank(oldToken)) {
            return new CodeMsgDataResponse(codeMsg.tokenErrorCode(), codeMsg.tokenErrorMsg());
        }
        String token = oldToken.substring(tokenPrefix.length());
        String username = jwtTokenUtils.getUsernameFromToken(token);
        CurrentUser currentUser = (CurrentUser) userDetailsService.loadUserByUsername(username);
        boolean canRefresh = jwtTokenUtils.canTokenBeRefreshed(token, currentUser.getLastPwdResetDate());
        if (canRefresh) {
            String newToken = jwtTokenUtils.refreshToken(token);
            long expireAt = jwtTokenUtils.getExpirationDateFromToken(newToken).getTime();
            TokenResponse tokenResponse = new TokenResponse(token, expireAt, currentUser.getRole());
            return new CodeMsgDataResponse<>(codeMsg.successCode(), codeMsg.successMsg(), tokenResponse);
        }
        return new CodeMsgDataResponse(codeMsg.failureCode(), codeMsg.failureMsg());
    }


    /**
     * 根据用户名(账户)查询用户的区域权限列表
     *
     * @param username
     * @return
     */
    public ApiResponse findAllAreaAuthoritiesByUsername(String username) {
        try {
            TypeReference<List<String>> typeReference = new TypeReference<List<String>>() {
            };

            List<AreaAuthorityResponse> result = new LinkedList<>();
            if (SecurityUtils.isRoot()) {
                for (Area area : areaDataService.findAllOrderByName()) {
                    AreaAuthorityResponse res = new AreaAuthorityResponse();
                    BeanUtils.copyProperties(area, res);
                    res.setDeviceAuthority(DeviceAuthority.AUTHORITY_CACHE);

                    result.add(res);
                }
            } else {
                for (UserAreaAuthority authority : areaAuthorityDataService.findAllByUsername(username)) {
                    AreaAuthorityResponse res = new AreaAuthorityResponse();
                    BeanUtils.copyProperties(authority.getArea(), res);
                    res.setDeviceAuthority(objectMapper.readValue(authority.getDeviceAuthority(), typeReference));

                    result.add(res);
                }
            }
            return new CodeMsgDataResponse<>(codeMsg.successCode(), codeMsg.successMsg(), result);
        } catch (IOException e) {
            LOGGER.error("Error process", e);
            return new CodeMsgDataResponse<>(codeMsg.failureCode(), codeMsg.failureMsg());
        }
    }

    //json转List
    public static <T> java.util.List<T> getListFromJson(String json, Class<T[]> clazz) {
        try {
            T[] arr = new Gson().fromJson(json, clazz);
            return Arrays.asList(arr);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取用户具有除霾器权限的区域列表(按区域名称排序)
     *
     * @return
     */
    public ApiResponse getAreasWithHcAuthority() {
        List<Area> areas;
        CurrentUser user = SecurityUtils.getCurrentUser();
        if (user.isRoot()) {
            areas = areaDataService.findAllOrderByName();
        } else {
            areas = areaAuthorityDataService.findAreasWithHazeCleanerAuthorityByUid(user.getId());
        }
        return new CodeMsgDataResponse<>(codeMsg.successCode(), codeMsg.successMsg(), areas);
    }

    /**
     * 获取用户具有某设备权限的区域列表(按区域名称排序)
     *
     * @param deviceType 设备类型
     * @return
     */
    public ApiResponse getAreasWithDeviceAuthority(Integer deviceType) {
        List<Area> areas;
        CurrentUser user = SecurityUtils.getCurrentUser();

        if (user.isRoot()) {
            areas = areaDataService.findAllOrderByName();
        } else {
            String deviceAuthority;
            switch (DeviceTypeEnum.fromType(deviceType)) {
                case LAMP:
                    deviceAuthority = DeviceAuthority.LAMP;
                    break;
                case CHARGING_PILE:
                    deviceAuthority = DeviceAuthority.CHARGE;
                    break;
                case HAZE_CLEANER:
                    deviceAuthority = DeviceAuthority.CLEANER;
                    break;
                case ADVERTISING_SCREEN:
                    deviceAuthority = DeviceAuthority.SCREEN;
                    break;
                case GEOMAGNETIC_SENSOR:
                    deviceAuthority = DeviceAuthority.DC;
                    break;
                case GEOMAGNETIC_RECEIVER:
                    deviceAuthority = DeviceAuthority.DC;
                    break;
                default:
                    throw new IllegalArgumentException();
            }
            areas = areaAuthorityDataService.findAreasWithDeviceAuthorityByUid(user.getId(), deviceAuthority);
        }
        return new CodeMsgDataResponse<>(codeMsg.successCode(), codeMsg.successMsg(), areas);
    }

    /**
     * 获取用户具有权限的所有设备的区域列表
     *
     * @return
     */
    public ApiResponse getAreasWithDevicesAuthority() {
        CurrentUser user = SecurityUtils.getCurrentUser();

        List<Area> gatewayAreas;
        List<Area> lampAreas;
        List<Area> chargeAreas;
        List<Area> cleanerAreas;
        List<Area> screenAreas;
        List<Area> dcAreas;
        if (user.isRoot()) {
            List<Area> areas = areaDataService.findAllOrderByName();
            gatewayAreas = areas;
            lampAreas = areas;
            chargeAreas = areas;
            cleanerAreas = areas;
            screenAreas = areas;
            dcAreas = areas;
        } else {
            gatewayAreas = new LinkedList<>();
            lampAreas = new LinkedList<>();
            chargeAreas = new LinkedList<>();
            cleanerAreas = new LinkedList<>();
            screenAreas = new LinkedList<>();
            dcAreas = new LinkedList<>();
            for (AreaAuthority authority : areaAuthorityDataService.findUserAreaAuthorities(user.getId())) {
                Area area = authority.getArea();
                String deviceAuthority = authority.getDeviceAuthority();
                if (deviceAuthority.contains(DeviceAuthority.LAMP)) {
                    lampAreas.add(area);
                    gatewayAreas.add(area);
                }
                if (deviceAuthority.contains(DeviceAuthority.CHARGE)) {
                    chargeAreas.add(area);
                }
                if (deviceAuthority.contains(DeviceAuthority.CLEANER)) {
                    cleanerAreas.add(area);
                }
                if (deviceAuthority.contains(DeviceAuthority.SCREEN)) {
                    screenAreas.add(area);
                }
                if (deviceAuthority.contains(DeviceAuthority.DC)) {
                    dcAreas.add(area);
                }
            }
        }

        Map<String, List<Area>> map = new HashMap<>(7);
        map.put("gateway", gatewayAreas);
        map.put("lamp", lampAreas);
        map.put("charge", chargeAreas);
        map.put("cleaner", cleanerAreas);
        map.put("screen", screenAreas);
        map.put("dcSensor", dcAreas);
        map.put("dcReceiver", dcAreas);
        return new CodeMsgDataResponse<>(codeMsg.successCode(), codeMsg.successMsg(), map);
    }

    /**
     * 保存用户区域权限
     *
     * @param user
     * @param authorityTreeDtos
     */
    private void saveAreaAuthorities(User user, List<AuthorityTreeDto> authorityTreeDtos) throws JsonProcessingException {
        List<UserAreaAuthority> authorities = new LinkedList<>();
        for (AuthorityTreeDto areaAuthority : authorityTreeDtos) {
            Area area = areaDataService.findByAid(areaAuthority.getId()).orElseThrow(IllegalArgumentException::new);
            UserAreaAuthority authority = new UserAreaAuthority();
            authority.setUser(user);
            authority.setArea(area);

            Set<String> set = new HashSet<>(8);
            List<String> list = new LinkedList<>(DeviceAuthority.AUTHORITY_CACHE);
            if (areaAuthority.getChildren() != null) {
                if (areaAuthority.getChildren().size() > 8) {
                    throw new BusinessException(codeMsg.illegalArgumentCode(), codeMsg.illegalArgumentMsg());
                }
                for (AuthorityTreeDto deviceAuthority : areaAuthority.getChildren()) {
                    set.add(deviceAuthority.getId());
                }

                list.removeIf(s -> !set.contains(s));
            }
            authority.setDeviceAuthority(objectMapper.writeValueAsString(list));

            authorities.add(authority);
        }
        areaAuthorityDataService.saveAll(authorities);
    }

    /**
     * 保存用户菜单权限
     *
     * @param user
     * @param authorityTreeDtos
     */
    private void saveMenuAuthorities(User user, List<AuthorityTreeDto> authorityTreeDtos) {
        LinkedList<UserMenuAuthority> authorities = new LinkedList<>();

        LinkedList<AuthorityTreeDto> stack = new LinkedList<>();
        stack.addAll(authorityTreeDtos);

        AuthorityTreeDto p;
        while (!stack.isEmpty()) {
            p = stack.pop();
            if (p.getChildren() == null || p.getChildren().isEmpty()) {
                UserMenuAuthority authority = new UserMenuAuthority();
                authority.setUser(user);
                authority.setMenu(p.getId());
                authorities.addFirst(authority);
            } else {
                stack.addAll(p.getChildren());
            }
        }
        menuAuthorityDataService.saveAll(authorities);
    }
}
